﻿using Melanchall.DryWetMidi.Core;
using Melanchall.DryWetMidi.Interaction;
using SFML.Audio;
using SFML.Graphics;
using SFML.System;

namespace Midisplay
{
    internal class NoteDisplayer : Drawable
    {
        private MidiFile _midiFile;
        private TempoMap _tempoMap;

        private Music _music;
        private float _musicLength;

        int whiteKeyCount = 75;
        int blackKeyCount = 53;

        public static float WhiteKeyWidth;
        float whiteKeyHeight;

        public static float BlackKeyWidth;
        float blackKeyHeight;

        float startX;
        float startY;
        float offsetX;

        float noteFallLength;
        float advanceTime;
        public static float NoteLengthPerSec;

        private float _currentTime;
        private bool _isRunning;

        private KeyData[] _keys;
        private List<NoteData> _spawnedNoteList = new List<NoteData>();

        private Sprite _background;

        public NoteDisplayer(string midiPath, string audioPath, float windowWidth, float windowHeight)
        {
            _midiFile = MidiFile.Read(midiPath);
            _tempoMap = _midiFile.GetTempoMap();
            _background = new Sprite();

            _music = new Music(audioPath);
            _music.Loop = false;

            _musicLength = _music.Duration.AsSeconds();

            _keys = new KeyData[whiteKeyCount + blackKeyCount];

            SetKeyboard(windowWidth, windowHeight);

            noteFallLength = windowHeight - whiteKeyHeight;
            advanceTime = 3f;
            NoteLengthPerSec = noteFallLength / advanceTime;

            SpawnNotes();

            _currentTime = -advanceTime;
            _isRunning = false;
        }

        public void Play()
        {
            _isRunning = true;
            if (_currentTime >= 0f)
            {
                _music.Play();
                _music.PlayingOffset = Time.FromSeconds(_currentTime);
                
            }
        }

        public void Pause()
        {
            _isRunning = false;
            _music.Pause();
        }

        public void Forward()
        {
            _currentTime = MathF.Min(_currentTime + 0.05f, _musicLength);
            _music.PlayingOffset = Time.FromSeconds(_currentTime);
        }

        public void Rewind()
        {
            _currentTime = MathF.Max(_currentTime - 0.05f, -advanceTime);
            _music.PlayingOffset = Time.FromSeconds(_currentTime);
        }

        public void Toggle()
        {
            if (_isRunning == true)
            {
                Pause();
            }
            else
            {
                Play();
            }
        }

        public void Reset()
        {
            _music.Stop();
            _currentTime = -advanceTime;
            _isRunning = false;
        }

        public void SetBackground(string path, float windowWidth, float windowHeight)
        {
            Texture texture = new Texture(path);
            _background = new Sprite(texture);
            _background.Scale = new Vector2f(1f * windowWidth / texture.Size.X, 1f * windowHeight / texture.Size.Y);
        }

        private void SetKeyboard(float windowWidth, float windowHeight)
        {
            WhiteKeyWidth = 1f / whiteKeyCount * windowWidth;
            whiteKeyHeight = windowHeight >= 140 ? 140f : windowHeight / 5f;

            BlackKeyWidth = 1f / 2.2f * WhiteKeyWidth;
            blackKeyHeight = 9.5f / 14.5f * whiteKeyHeight;

            startX = 0f;
            startY = windowHeight - whiteKeyHeight;
            offsetX = WhiteKeyWidth - BlackKeyWidth / 2f;

            for (int i = 0; i < whiteKeyCount; i++)
            {
                var whiteKey = new RectangleShape();
                whiteKey.Size = new Vector2f(WhiteKeyWidth, whiteKeyHeight);
                whiteKey.Position = new Vector2f(startX + i * WhiteKeyWidth, startY);
                whiteKey.FillColor = Color.White;
                whiteKey.OutlineColor = Color.Black;
                whiteKey.OutlineThickness = 1f;
                int id = i / 7 * 12 + 2 * (i % 7) - (int)MathF.Floor(i % 7 / 3);
                _keys[id] = new KeyData(id, true, whiteKey);
            }

            for (int i = 0; i < blackKeyCount; i++)
            {
                var blackKey = new RectangleShape();
                blackKey.Size = new Vector2f(BlackKeyWidth, blackKeyHeight);
                blackKey.Position =
                    new Vector2f(startX + offsetX + (i / 5 * 7 + i % 5 + MathF.Floor(i % 5 / 2)) * WhiteKeyWidth, startY);
                blackKey.FillColor = Color.Black;
                blackKey.OutlineColor = Color.Black;
                blackKey.OutlineThickness = 1f;
                int id = i / 5 * 12 + 1 + 2 * (i % 5) + (int)MathF.Floor(i % 5 / 2);
                _keys[id] = new KeyData(id, false, blackKey);
            }
        }

        private void SpawnNotes()
        {
            List<Note> noteList = _midiFile.GetNotes().ToList();
            for (int i = 0; i < noteList.Count; i++)
            {
                float noteStartTime = (float)noteList[i].TimeAs<MetricTimeSpan>(_tempoMap).TotalSeconds;
                float noteEndTime = (float)noteList[i].EndTimeAs<MetricTimeSpan>(_tempoMap).TotalSeconds;
                var noteData = new NoteData(noteList[i].NoteNumber, noteStartTime, noteEndTime);
                _spawnedNoteList.Add(noteData);
            }
        }

        public void Update(float deltaTime)
        {
            UpdateKeyboard();

            UpdateNotePosition();

            if (_isRunning == false)
            {
                return;
            }

            if (_currentTime >= 0 && _isRunning == true && _music.Status != SoundStatus.Playing)
            {
                _music.Play();
                _music.PlayingOffset = Time.FromSeconds(_currentTime);
            }

            if (_currentTime >= _musicLength)
            {
                Reset();
            }

            _currentTime += deltaTime;
        }

        private void UpdateKeyboard()
        {
            for (int i = 0; i < _keys.Length; i++)
            {
                _keys[i].OnKeyUp();
                for (int j = 0; j < _spawnedNoteList.Count; j++)
                {
                    if (i == _spawnedNoteList[j].ID
                        && _spawnedNoteList[j].StartTime <= _currentTime
                        && _spawnedNoteList[j].EndTime >= _currentTime)
                    {
                        _keys[i].OnKeyDown();
                        break;
                    }
                }
            }
        }

        public void UpdateNotePosition()
        {
            for (int i = 0; i < _spawnedNoteList.Count; i++)
            {
                float x = _keys[_spawnedNoteList[i].ID].Shape.Position.X;
                float y = noteFallLength - (_spawnedNoteList[i].EndTime - _currentTime) * NoteLengthPerSec;
                _spawnedNoteList[i].UpdatePosition(x, y);
            }
        }

        public void Draw(RenderTarget target, RenderStates states)
        {
            target.Draw(_background, states);

            for (int i = 0; i < _spawnedNoteList.Count; i++)
            {
                target.Draw(_spawnedNoteList[i], states);
            }

            for (int i = 0; i < _keys.Length; i++)
            {
                if (_keys[i].WhiteOrBlack == true)
                {
                    target.Draw(_keys[i].Shape, states);
                }
            }

            for (int i = 0; i < _keys.Length; i++)
            {
                if (_keys[i].WhiteOrBlack == false)
                {
                    target.Draw(_keys[i].Shape, states);
                }
            }
        }

        public void Destroy()
        {
            Pause();
            _music.Dispose();
        }
    }
}
